// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module c

import v.ast
import v.token
import v.util

enum SqlExprSide {
	left
	right
}

fn (mut g Gen) sql_stmt(node ast.SqlStmt) {
	connection_var_name := g.new_tmp_var()

	g.write_orm_connection_init(connection_var_name, &node.db_expr)

	for line in node.lines {
		g.sql_stmt_line(line, connection_var_name, node.or_expr)
	}
}

fn (mut g Gen) sql_stmt_line(nd ast.SqlStmtLine, expr string, or_expr ast.OrExpr) {
	mut node := nd
	table_name := g.get_table_name(node.table_expr)
	g.sql_table_name = g.table.sym(node.table_expr.typ).name
	res := g.new_tmp_var()

	if node.kind != .create {
		mut fields := []ast.StructField{}
		for f in node.fields {
			mut skip := false
			mut primary := false
			for attr in f.attrs {
				if attr.name == 'primary' {
					primary = true
				}
				if attr.name == 'skip' {
					skip = true
				}
			}
			if !skip && !primary {
				fields << f
			}
		}
		node.fields = fields.clone()
		unsafe { fields.free() }
	}
	if node.kind == .create {
		g.write('${result_name}_void ${res} = orm__Connection_name_table[${expr}._typ]._method_')
		g.sql_create_table(node, expr, table_name)
	} else if node.kind == .drop {
		g.write('${result_name}_void ${res} = orm__Connection_name_table[${expr}._typ]._method_')
		g.writeln('drop(${expr}._object, _SLIT("${table_name}"));')
	} else if node.kind == .insert {
		arr := g.new_tmp_var()
		g.writeln('Array_orm__Primitive ${arr} = __new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0);')
		g.sql_insert(node, expr, table_name, arr, res, '', '', or_expr)
	} else if node.kind == .update {
		g.write('${result_name}_void ${res} = orm__Connection_name_table[${expr}._typ]._method_')
		g.sql_update(node, expr, table_name)
	} else if node.kind == .delete {
		g.write('${result_name}_void ${res} = orm__Connection_name_table[${expr}._typ]._method_')
		g.sql_delete(node, expr, table_name)
	}

	if or_expr.kind == .block {
		g.or_block(res, or_expr, ast.int_type.set_flag(.result))
	} else if or_expr.kind == .absent {
		g.write_error_handling_for_orm_result(node.pos, res)
	}
}

fn (mut g Gen) sql_create_table(node ast.SqlStmtLine, expr string, table_name string) {
	g.write('create(${expr}._object, _SLIT("${table_name}"), new_array_from_c_array(${node.fields.len}, ${node.fields.len}, sizeof(orm__TableField),')
	if node.fields.len > 0 {
		g.write(' _MOV((orm__TableField[${node.fields.len}]){')
		for field in node.fields {
			sym := g.table.sym(field.typ)
			g.write('(orm__TableField){')
			g.write('.name = _SLIT("${field.name}"),')
			mut typ := int(field.typ)
			if sym.name == 'time.Time' {
				typ = -2
			}
			g.write('.typ = ${typ},')
			g.write('.is_arr = ${sym.kind == .array}, ')
			g.write('.is_time = ${g.table.get_type_name(field.typ) == 'time__Time'},')
			g.write('.default_val = (string){.str = (byteptr) "${field.default_val}", .is_lit = 1},')
			g.write('.attrs = new_array_from_c_array(${field.attrs.len}, ${field.attrs.len}, sizeof(StructAttribute),')
			if field.attrs.len > 0 {
				g.write(' _MOV((StructAttribute[${field.attrs.len}]){')
				for attr in field.attrs {
					g.write('(StructAttribute){')
					g.write('.name = _SLIT("${attr.name}"),')
					g.write('.has_arg = ${attr.has_arg},')
					g.write('.arg = _SLIT("${attr.arg}"),')
					g.write('.kind = ${int(attr.kind)},')
					g.write('},')
				}
				g.write('})')
			} else {
				g.write('NULL')
			}
			g.write(')')
			g.write('},')
		}
		g.write('})')
	} else {
		g.write('NULL')
	}
	g.writeln('));')
}

fn (mut g Gen) sql_insert(node ast.SqlStmtLine, expr string, table_name string, last_ids_arr string, res string, pid string, fkey string, or_expr ast.OrExpr) {
	mut subs := []ast.SqlStmtLine{}
	mut arrs := []ast.SqlStmtLine{}
	mut fkeys := []string{}
	mut field_names := []string{}

	for f in node.fields {
		sym := g.table.sym(f.typ)
		if sym.kind == .struct_ && sym.name != 'time.Time' {
			subs << node.sub_structs[int(f.typ)]
		} else if sym.kind == .array {
			mut f_key := ''
			for attr in f.attrs {
				if attr.name == 'fkey' && attr.has_arg {
					if attr.kind == .string {
						f_key = attr.arg
					} else {
						verror("fkey attribute need be string. Try [fkey: '${attr.arg}'] instead of [fkey: ${attr.arg}]")
					}
				}
			}
			if f_key == '' {
				verror('An field which holds an array, needs a fkey defined')
			}
			fkeys << f_key
			info := sym.array_info()
			if info.nr_dims == 1 {
				arrs << node.sub_structs[int(info.elem_type)]
				field_names << f.name
			} else {
				verror('V ORM only supports 1 dimensional arrays')
			}
		}
	}

	fields := node.fields.filter(g.table.sym(it.typ).kind != .array)

	for sub in subs {
		g.sql_stmt_line(sub, expr, or_expr)
		g.writeln('array_push(&${last_ids_arr}, _MOV((orm__Primitive[]){orm__int_to_primitive(orm__Connection_name_table[${expr}._typ]._method_last_id(${expr}._object))}));')
	}

	g.write('${result_name}_void ${res} = orm__Connection_name_table[${expr}._typ]._method_')
	g.write('insert(${expr}._object, _SLIT("${table_name}"), (orm__QueryData){')

	g.write('.fields = new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(string),')
	if fields.len > 0 {
		g.write('_MOV((string[${fields.len}]){')
		for f in fields {
			g.write('_SLIT("${g.get_field_name(f)}"),')
		}
		g.write('})')
	} else {
		g.write('NULL')
	}
	g.write('),')

	mut member_access_type := '.'

	if node.scope != unsafe { nil } {
		inserting_object := node.scope.find(node.object_var_name) or { verror(err.str()) }

		if inserting_object.typ.is_ptr() {
			member_access_type = '->'
		}
	}

	g.write('.data = new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(orm__Primitive),')
	if fields.len > 0 {
		g.write(' _MOV((orm__Primitive[${fields.len}]){')
		mut structs := 0
		for f in fields {
			if f.name == fkey {
				g.write('${pid}, ')
				continue
			}
			mut sym := g.table.sym(f.typ)
			mut typ := sym.cname
			if sym.kind == .struct_ && typ != 'time__Time' {
				g.write('(*(orm__Primitive*) array_get(${last_ids_arr}, ${structs})),')
				structs++
				continue
			}
			if typ == 'time__Time' {
				typ = 'time'
			}

			g.write('orm__${typ}_to_primitive(${node.object_var_name}${member_access_type}${c_name(f.name)}),')
		}
		g.write('})')
	} else {
		g.write('NULL')
	}
	g.write('),')
	g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
	g.write('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
	g.write('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
	g.writeln('});')

	if arrs.len > 0 {
		mut id_name := g.new_tmp_var()
		g.writeln('orm__Primitive ${id_name} = orm__int_to_primitive(orm__Connection_name_table[${expr}._typ]._method_last_id(${expr}._object));')
		for i, mut arr in arrs {
			c_field_name := c_name(field_names[i])
			idx := g.new_tmp_var()
			g.writeln('for (int ${idx} = 0; ${idx} < ${arr.object_var_name}${member_access_type}${c_field_name}.len; ${idx}++) {')
			last_ids := g.new_tmp_var()
			res_ := g.new_tmp_var()
			tmp_var := g.new_tmp_var()
			ctyp := g.typ(arr.table_expr.typ)
			g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)array_get(${arr.object_var_name}${member_access_type}${c_field_name}, ${idx}));')
			arr.object_var_name = tmp_var
			mut fff := []ast.StructField{}
			for f in arr.fields {
				mut skip := false
				mut primary := false
				for attr in f.attrs {
					if attr.name == 'primary' {
						primary = true
					}
					if attr.name == 'skip' {
						skip = true
					}
				}
				if !skip && !primary {
					fff << f
				}
			}
			arr.fields = fff.clone()
			unsafe { fff.free() }
			g.sql_insert(arr, expr, g.get_table_name(arr.table_expr), last_ids, res_,
				id_name, fkeys[i], or_expr)
			g.writeln('}')
		}
	}
}

fn (mut g Gen) sql_update(node ast.SqlStmtLine, expr string, table_name string) {
	g.write('update(${expr}._object, _SLIT("${table_name}"), (orm__QueryData){')
	g.write('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
	g.write('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
	g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
	g.write('.parentheses = __new_array_with_default_noscan(0, 0, sizeof(Array_int), 0),')
	if node.updated_columns.len > 0 {
		g.write('.fields = new_array_from_c_array(${node.updated_columns.len}, ${node.updated_columns.len}, sizeof(string),')
		g.write(' _MOV((string[${node.updated_columns.len}]){')
		for field in node.updated_columns {
			g.write('_SLIT("${field}"),')
		}
		g.write('})')
	} else {
		g.write('.fields = __new_array_with_default_noscan(${node.updated_columns.len}, ${node.updated_columns.len}, sizeof(string), 0')
	}
	g.write('),')
	g.write('.data = new_array_from_c_array(${node.update_exprs.len}, ${node.update_exprs.len}, sizeof(orm__Primitive),')
	if node.update_exprs.len > 0 {
		g.write(' _MOV((orm__Primitive[${node.update_exprs.len}]){')
		for e in node.update_exprs {
			g.sql_expr_to_orm_primitive(e)
		}
		g.write('})')
	}
	g.write('),},')
	g.sql_gen_where_data(node.where_expr)
	g.writeln(');')
}

fn (mut g Gen) sql_delete(node ast.SqlStmtLine, expr string, table_name string) {
	g.write('_v_delete(${expr}._object, _SLIT("${table_name}"),')
	g.sql_gen_where_data(node.where_expr)
	g.writeln(');')
}

fn (mut g Gen) sql_expr_to_orm_primitive(expr ast.Expr) {
	match expr {
		ast.InfixExpr {
			g.sql_write_orm_primitive(g.table.find_type_idx('orm.InfixType'), expr)
		}
		ast.StringLiteral {
			g.sql_write_orm_primitive(ast.string_type, expr)
		}
		ast.StringInterLiteral {
			g.sql_write_orm_primitive(ast.string_type, expr)
		}
		ast.IntegerLiteral {
			g.sql_write_orm_primitive(ast.int_type, expr)
		}
		ast.BoolLiteral {
			g.sql_write_orm_primitive(ast.bool_type, expr)
		}
		ast.Ident {
			info := expr.info as ast.IdentVar
			g.sql_write_orm_primitive(info.typ, expr)
		}
		ast.SelectorExpr {
			g.sql_write_orm_primitive(expr.typ, expr)
		}
		ast.CallExpr {
			g.sql_write_orm_primitive(expr.return_type, expr)
		}
		else {
			eprintln(expr)
			verror('V ORM: ${expr.type_name()} is not supported')
		}
	}
}

fn (mut g Gen) sql_write_orm_primitive(t ast.Type, expr ast.Expr) {
	mut sym := g.table.sym(t)
	mut typ := sym.cname
	if typ == 'orm__Primitive' {
		g.expr(expr)
		g.write(',')
		return
	}
	if typ == 'time__Time' {
		typ = 'time'
	}
	if typ == 'orm__InfixType' {
		typ = 'infix'
	}

	g.write('orm__${typ}_to_primitive(')
	if expr is ast.InfixExpr {
		g.write('(orm__InfixType){')
		g.write('.name = _SLIT("${expr.left}"),')
		mut kind := match expr.op {
			.plus {
				'orm__MathOperationKind__add'
			}
			.minus {
				'orm__MathOperationKind__sub'
			}
			.div {
				'orm__MathOperationKind__div'
			}
			.mul {
				'orm__MathOperationKind__mul'
			}
			else {
				''
			}
		}
		g.write('.operator = ${kind},')
		g.write('.right = ')
		g.sql_expr_to_orm_primitive(expr.right)
		g.write('}')
	} else if expr is ast.CallExpr {
		g.call_expr(expr)
	} else {
		g.expr(expr)
	}
	g.write('),')
}

fn (mut g Gen) sql_where_data(expr ast.Expr, mut fields []string, mut parentheses [][]int, mut kinds []string, mut data []ast.Expr, mut is_and []bool) {
	match expr {
		ast.InfixExpr {
			g.sql_side = .left
			g.sql_where_data(expr.left, mut fields, mut parentheses, mut kinds, mut data, mut
				is_and)
			mut kind := match expr.op {
				.ne {
					'orm__OperationKind__neq'
				}
				.eq {
					'orm__OperationKind__eq'
				}
				.lt {
					'orm__OperationKind__lt'
				}
				.gt {
					'orm__OperationKind__gt'
				}
				.ge {
					'orm__OperationKind__ge'
				}
				.le {
					'orm__OperationKind__le'
				}
				else {
					''
				}
			}
			if kind == '' {
				if expr.op == .logical_or {
					is_and << false
				} else if expr.op == .and {
					is_and << true
				} else {
					kind = 'orm__OperationKind__eq'
				}
			}
			if expr.left !is ast.InfixExpr && expr.right !is ast.InfixExpr && kind != '' {
				kinds << kind
			}
			g.sql_side = .right
			g.sql_where_data(expr.right, mut fields, mut parentheses, mut kinds, mut data, mut
				is_and)
		}
		ast.ParExpr {
			mut par := [fields.len]
			g.sql_where_data(expr.expr, mut fields, mut parentheses, mut kinds, mut data, mut
				is_and)
			par << fields.len - 1
			parentheses << par
		}
		ast.Ident {
			if g.sql_side == .left {
				fields << g.get_field_name(g.get_struct_field(expr.name))
			} else {
				data << expr
			}
		}
		ast.StringLiteral {
			data << expr
		}
		ast.StringInterLiteral {
			data << expr
		}
		ast.IntegerLiteral {
			data << expr
		}
		ast.SelectorExpr {
			data << expr
		}
		ast.BoolLiteral {
			data << expr
		}
		ast.CallExpr {
			data << expr
		}
		else {}
	}
}

fn (mut g Gen) sql_gen_where_data(where_expr ast.Expr) {
	g.write('(orm__QueryData){')
	mut fields := []string{}
	mut kinds := []string{}
	mut parentheses := [][]int{}
	mut data := []ast.Expr{}
	mut is_and := []bool{}
	g.sql_where_data(where_expr, mut fields, mut parentheses, mut kinds, mut data, mut
		is_and)
	g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
	if fields.len > 0 {
		g.write('.fields = new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(string),')
		g.write(' _MOV((string[${fields.len}]){')
		for field in fields {
			g.write('_SLIT("${field}"),')
		}
		g.write('})')
	} else {
		g.write('.fields = __new_array_with_default_noscan(${fields.len}, ${fields.len}, sizeof(string), 0')
	}
	g.write('),')

	g.write('.data = new_array_from_c_array(${data.len}, ${data.len}, sizeof(orm__Primitive),')
	if data.len > 0 {
		g.write(' _MOV((orm__Primitive[${data.len}]){')
		for e in data {
			g.sql_expr_to_orm_primitive(e)
		}
		g.write('})')
	}
	g.write('),')

	g.write('.parentheses = ')
	if parentheses.len > 0 {
		g.write('new_array_from_c_array(${parentheses.len}, ${parentheses.len}, sizeof(Array_int), _MOV((Array_int[${parentheses.len}]){')
		for par in parentheses {
			if par.len > 0 {
				g.write('new_array_from_c_array(${par.len}, ${par.len}, sizeof(int), _MOV((int[${par.len}]){')
				for val in par {
					g.write('${val},')
				}
				g.write('})),')
			} else {
				g.write('__new_array_with_default_noscan(0, 0, sizeof(int), 0),')
			}
		}
		g.write('}))')
	} else {
		g.write('__new_array_with_default_noscan(0, 0, sizeof(Array_int), 0)')
	}
	g.write(',')

	if kinds.len > 0 {
		g.write('.kinds = new_array_from_c_array(${kinds.len}, ${kinds.len}, sizeof(orm__OperationKind),')
		g.write(' _MOV((orm__OperationKind[${kinds.len}]){')
		for k in kinds {
			g.write('${k},')
		}
		g.write('})')
	} else {
		g.write('.kinds = __new_array_with_default_noscan(${kinds.len}, ${kinds.len}, sizeof(orm__OperationKind), 0')
	}
	g.write('),')

	if is_and.len > 0 {
		g.write('.is_and = new_array_from_c_array(${is_and.len}, ${is_and.len}, sizeof(bool),')
		g.write(' _MOV((bool[${is_and.len}]){')
		for b in is_and {
			g.write('${b}, ')
		}
		g.write('})')
	} else {
		g.write('.is_and = __new_array_with_default_noscan(${is_and.len}, ${is_and.len}, sizeof(bool), 0')
	}
	g.write('),}')
}

fn (mut g Gen) sql_select_expr(node ast.SqlExpr) {
	left := g.go_before_stmt(0)
	connection_var_name := g.new_tmp_var()
	g.writeln('')

	g.write_orm_connection_init(connection_var_name, &node.db_expr)

	g.sql_select(node, connection_var_name, left, node.or_expr)
}

fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string, or_expr ast.OrExpr) {
	mut fields := []ast.StructField{}
	mut prim := ''
	for f in node.fields {
		mut skip := false
		for attr in f.attrs {
			if attr.name == 'primary' {
				prim = f.name
			}
			if attr.name == 'skip' {
				skip = true
			}
		}
		if !skip {
			fields << f
		}
	}

	res := g.new_tmp_var()
	table_name := g.get_table_name(node.table_expr)
	g.sql_table_name = g.table.sym(node.table_expr.typ).name
	g.write('${result_name}_Array_Array_orm__Primitive _o${res} = orm__Connection_name_table[${expr}._typ]._method_select(${expr}._object, ')
	g.write('(orm__SelectConfig){')
	g.write('.table = _SLIT("${table_name}"),')
	g.write('.is_count = ${node.is_count},')
	g.write('.has_where = ${node.has_where},')
	g.write('.has_order = ${node.has_order},')
	if node.has_order {
		g.write('.order = _SLIT("')
		g.expr(node.order_expr)
		g.write('"),')
		if node.has_desc {
			g.write('.order_type = orm__OrderType__desc,')
		} else {
			g.write('.order_type = orm__OrderType__asc,')
		}
	}
	g.write('.has_limit = ${node.has_limit},')
	g.write('.has_offset = ${node.has_offset},')
	if prim != '' {
		g.write('.primary = _SLIT("${prim}"),')
	}
	select_fields := fields.filter(g.table.sym(it.typ).kind != .array)
	g.write('.fields = new_array_from_c_array(${select_fields.len}, ${select_fields.len}, sizeof(string),')
	mut types := []int{}
	if select_fields.len > 0 {
		g.write(' _MOV((string[${select_fields.len}]){')
		for field in select_fields {
			g.write('_SLIT("${g.get_field_name(field)}"),')
			sym := g.table.sym(field.typ)
			if sym.name == 'time.Time' {
				types << -2
				continue
			}
			if sym.kind == .struct_ {
				types << int(ast.int_type)
				continue
			}
			types << int(field.typ)
		}
		g.write('})')
	} else {
		g.write('NULL')
	}
	g.write('),')
	g.write('.types = new_array_from_c_array(${types.len}, ${types.len}, sizeof(int),')
	if types.len > 0 {
		g.write(' _MOV((int[${types.len}]){')
		for typ in types {
			g.write('${typ},')
		}
		g.write('})')
	} else {
		g.write('NULL')
	}
	g.write('),},')

	mut exprs := []ast.Expr{}
	if node.has_limit {
		exprs << node.limit_expr
	}
	if node.has_offset {
		exprs << node.offset_expr
	}
	g.write('(orm__QueryData) {')
	g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
	g.write('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
	g.write('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
	g.write('.parentheses = __new_array_with_default_noscan(0, 0, sizeof(Array_int), 0),')
	if exprs.len > 0 {
		g.write('.data = new_array_from_c_array(${exprs.len}, ${exprs.len}, sizeof(orm__Primitive),')
		g.write(' _MOV((orm__Primitive[${exprs.len}]){')
		for e in exprs {
			g.sql_expr_to_orm_primitive(e)
		}
		g.write('})')
	} else {
		g.write('.data = __new_array_with_default_noscan(${exprs.len}, ${exprs.len}, sizeof(orm__Primitive), 0')
	}
	g.write(')},')

	if node.has_where {
		g.sql_gen_where_data(node.where_expr)
	} else {
		g.write('(orm__QueryData) {')
		g.write('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
		g.write('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
		g.write('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
		g.write('.parentheses = __new_array_with_default_noscan(0, 0, sizeof(Array_int), 0),')
		g.write('.data = __new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0)')
		g.write('}')
	}
	g.writeln(');')

	mut tmp_left := g.new_tmp_var()
	g.writeln('${g.typ(node.typ.set_flag(.result))} ${tmp_left};')

	if node.or_expr.kind == .block {
		g.writeln('${tmp_left}.is_error = _o${res}.is_error;')
		g.writeln('${tmp_left}.err = _o${res}.err;')
		g.or_block(tmp_left, node.or_expr, node.typ.set_flag(.result))
		g.writeln('else {')
		g.indent++
	} else if node.or_expr.kind == .absent {
		g.write_error_handling_for_orm_result(node.pos, '_o${res}')
	}

	g.writeln('Array_Array_orm__Primitive ${res} = (*(Array_Array_orm__Primitive*)_o${res}.data);')

	if node.is_count {
		g.writeln('*(${g.typ(node.typ)}*) ${tmp_left}.data = *((*(orm__Primitive*) array_get((*(Array_orm__Primitive*)array_get(${res}, 0)), 0))._int);')
		if node.or_expr.kind == .block {
			g.indent--
			g.writeln('}')
		}
	} else {
		tmp := g.new_tmp_var()
		styp := g.typ(node.typ)
		idx := g.new_tmp_var()
		g.writeln('int ${idx} = 0;')
		mut typ_str := ''
		if node.is_array {
			info := g.table.sym(node.typ).array_info()
			typ_str = g.typ(info.elem_type)
			g.writeln('${styp} ${tmp}_array = __new_array(0, ${res}.len, sizeof(${typ_str}));')
			g.writeln('for (; ${idx} < ${res}.len; ${idx}++) {')
			g.indent++
			g.write('${typ_str} ${tmp} = (${typ_str}) {')
			inf := g.table.sym(info.elem_type).struct_info()
			for i, field in inf.fields {
				g.zero_struct_field(field)
				if i != inf.fields.len - 1 {
					g.write(', ')
				}
			}
			g.writeln('};')
		} else {
			g.write('${styp} ${tmp} = (${styp}){')
			info := g.table.sym(node.typ).struct_info()
			for i, field in info.fields {
				g.zero_struct_field(field)
				if i != info.fields.len - 1 {
					g.write(', ')
				}
			}
			g.writeln('};')
		}

		g.writeln('if (${res}.len > 0) {')
		g.indent++
		for i, field in fields {
			sel := '(*(orm__Primitive*) array_get((*(Array_orm__Primitive*) array_get(${res}, ${idx})), ${i}))'
			sym := g.table.sym(field.typ)
			if sym.kind == .struct_ && sym.name != 'time.Time' {
				mut sub := node.sub_structs[int(field.typ)]
				mut where_expr := sub.where_expr as ast.InfixExpr
				mut ident := where_expr.right as ast.Ident
				name := sel
				s := g.table.find_type_idx('orm.Primitive')
				if s != 0 {
					if mut ident.info is ast.IdentVar {
						ident.info.typ = s
					}
				}
				ident.name = name
				where_expr.right = ident
				sub.where_expr = where_expr

				g.sql_select(sub, expr, '${tmp}.${c_name(field.name)} = ', or_expr)
			} else if sym.kind == .array {
				mut fkey := ''
				for attr in field.attrs {
					if attr.name == 'fkey' && attr.has_arg {
						if attr.kind == .string {
							fkey = attr.arg
						} else {
							verror("fkey attribute need be string. Try [fkey: '${attr.arg}'] instead of [fkey: ${attr.arg}]")
						}
					}
				}
				if fkey == '' {
					verror('An field which holds an array, needs a fkey defined')
				}
				info := sym.array_info()
				arr_typ := info.elem_type
				sub := node.sub_structs[int(arr_typ)]
				mut where_expr := sub.where_expr as ast.InfixExpr
				mut l := where_expr.left as ast.Ident
				mut r := where_expr.right as ast.Ident
				l.name = fkey
				r.name = tmp
				where_expr.left = l
				where_expr.right = ast.SelectorExpr{
					pos: r.pos
					field_name: prim
					is_mut: false
					expr: r
					expr_type: (r.info as ast.IdentVar).typ
					typ: ast.int_type
					scope: 0
				}
				mut arr := ast.SqlExpr{
					typ: field.typ
					is_count: sub.is_count
					db_expr: sub.db_expr
					has_where: sub.has_where
					has_offset: sub.has_offset
					offset_expr: sub.offset_expr
					has_order: sub.has_order
					order_expr: sub.order_expr
					has_desc: sub.has_desc
					is_array: true
					pos: sub.pos
					has_limit: sub.has_limit
					limit_expr: sub.limit_expr
					table_expr: sub.table_expr
					fields: sub.fields
					where_expr: where_expr
				}

				g.sql_select(arr, expr, '${tmp}.${c_name(field.name)} = ', or_expr)
			} else {
				mut typ := sym.cname
				g.writeln('${tmp}.${c_name(field.name)} = *(${sel}._${typ});')
			}
		}
		g.indent--
		g.writeln('}')

		if node.is_array {
			g.writeln('array_push(&${tmp}_array, _MOV((${typ_str}[]){ ${tmp} }));')
			g.indent--
			g.writeln('}')
		}

		g.write('*(${g.typ(node.typ)}*) ${tmp_left}.data = ${tmp}')
		if node.is_array {
			g.write('_array')
		}
		g.writeln(';')
		if node.or_expr.kind == .block {
			g.indent--
			g.writeln('}')
		}
	}
	g.write('${left} *(${g.typ(node.typ)}*) ${tmp_left}.data')
	if !g.inside_call {
		g.writeln(';')
	}
}

fn (mut g Gen) get_db_type(expr ast.Expr) ?ast.Type {
	match expr {
		ast.Ident {
			if expr.info is ast.IdentVar {
				return g.table.unaliased_type(expr.info.typ)
			}
		}
		ast.SelectorExpr {
			return g.table.unaliased_type(expr.typ)
		}
		else {
			return none
		}
	}
	return none
}

fn (mut g Gen) get_table_name(table_expr ast.TypeNode) string {
	info := g.table.sym(table_expr.typ).struct_info()
	mut tablename := util.strip_mod_name(g.table.sym(table_expr.typ).name)
	for attr in info.attrs {
		if attr.kind == .string && attr.name == 'table' && attr.arg != '' {
			tablename = attr.arg
			break
		}
	}
	return tablename
}

fn (mut g Gen) get_struct_field(name string) ast.StructField {
	info := g.table.sym(g.table.type_idxs[g.sql_table_name]).struct_info()
	mut f := ast.StructField{}
	for field in info.fields {
		if field.name == name {
			f = field
		}
	}
	return f
}

fn (mut g Gen) get_field_name(field ast.StructField) string {
	mut name := field.name
	for attr in field.attrs {
		if attr.kind == .string && attr.name == 'sql' && attr.arg != '' {
			name = attr.arg
			break
		}
	}
	sym := g.table.sym(field.typ)
	if sym.kind == .struct_ && sym.name != 'time.Time' {
		name = '${name}_id'
	}
	return name
}

fn (mut g Gen) write_error_handling_for_orm_result(expr_pos &token.Pos, result_var_name string) {
	g.writeln('if (${result_var_name}.is_error) {')

	if g.pref.is_debug {
		g.write_v_source_line_info(expr_pos)
		paline, pafile, pamod, pafn := g.panic_debug_info(expr_pos)
		g.write('\tpanic_debug(${paline}, tos3("${pafile}"), tos3("${pamod}"), tos3("${pafn}"), IError_str(${result_var_name}.err) );')
	} else {
		g.writeln('\t_v_panic(IError_str(${result_var_name}.err));')
	}

	g.writeln('}')
}

fn (mut g Gen) write_orm_connection_init(connection_var_name string, db_expr &ast.Expr) {
	db_expr_type := g.get_db_type(db_expr) or { verror('V ORM: unknown db type for ${db_expr}') }

	mut db_ctype_name := g.typ(db_expr_type)
	is_pointer := db_ctype_name.ends_with('*')
	reference_sign := if is_pointer { '' } else { '&' }
	db_ctype_name = db_ctype_name.trim_right('*')

	g.writeln('// orm')
	g.write('orm__Connection ${connection_var_name} = ')

	if db_ctype_name == 'orm__Connection' {
		g.expr(db_expr)
		g.writeln(';')
	} else {
		g.write('(orm__Connection){._${db_ctype_name} = ${reference_sign}')
		g.expr(db_expr)
		g.writeln(', ._typ = _orm__Connection_${db_ctype_name}_index};')
	}
}
